//sturnfie-pov-clock.c for IMSAInterssion 2011 Example Code
	// Originally written by Lucas Sturnfield (sturnfie)

/*				Overview

    This firwmare implements a POV-clock. It is written to run on
    a PIC18F46k20 and for compilation by the MPLAB C18 LITE C Compiler.

    The firmware configures the internal oscillator module of the
    PIC18F46k20 to drive FOSC at an operational frequency of 16MHZ.
    FOSC is used as a Program Counter and to generate timed interrupts
    corresponding to increments of position and time.

    A Position is assumed to be an angular position of the LED board spinning
    at a somewhat constant rate. The LEDs are controlled at each angular
    position (OFF/ON). The LED pattern at a particular angular position
    is that position's LEDSTATE. PositionID is incremented on each timer0
    overflow. To speed up how quickly each the firmware cycles through the
    stored LEDSTATE sequences, configure timer0 to overflow more quickly.
    This effectively stretches or shrinks the displayed text. 
    
    The LEDSTATE for each angular position is all off for most positions, 
    or pulled from a framebuffer when at the angular positions where we want 
    to draw the clock-image.

    Each time the framebuffer holding the displayed clock image needs to update,
    a new set of LEDSTATES are stored into the framebuffer. A seperate process
    accesses the LEDSTATE for each angular position and sets the LEDs accordingly.

    The framebuffer is currently intended to hold one line of text. The framebuffer
    is currently intended to hold the LEDSTATES required to draw the pattern (HH:MM:SS).

    The LEDSTATES needed for each symbol are stored into a structure called a fontmap.
    This firmware implements an 11-entry fontmap (0-9, :). Each entry is an array of
    five 16-bit data structures. The sixteen bits correspond to the illumination
    states of 16 possible LEDs. There are five of these data structures due to
    the design decision to use 5 LEDSTATES to draw each symbol. Additionally only
    8 LEDs will be used to draw each symbol. Vertical transposition of the text can be
    configured by adjusting the TEXT_VERTICAL_ADJUST variable. The fontmap entries are
    self-indexable by their respective 0-9 digit, with index 10 holding the entry for
    the colon (:).

    When the clock text is written to the FRAMEBUFFER, a number of
    blank LEDSTATES are inserted between the characters to aid in readibility. This
    number is configured by adjusting SPACE_BETWEEN_SYMBOLS. After each sequence of 5
    LEDSTATES is stored into the FRAMEBUFFER, a SPACE_BETWEEN_SYMBOLS amount of blank
    LEDSTATES are inserted.

    This firmware uses two timer modules, Timer0 and Timer1.
    
    Timer0 is configured to generate an interrupt that signals when the board has spun
    to the next angular position. This firmware makes the assumption that the board is
    moving at a somewhat constant rate and that a time-based estimation of position is
    functionally acceptable. To help with the error skew, this firmware uses a single
    position sensor to calibrate the position estimation with every revolution. The
    PositionID variable is  reset to the starting position each time we reach the
    position sensor. The LEDSTATE is updated for each angular position in the
    timer0Handler function.

    Timer1 is configured to generate an interrupt that signals when 1 second has elapsed. 
    Simple logic is used to accumulate the seconds, minutes, and hours.

    At the triggered 1-second intervals the new clock-time image is stored to the
    FRAMEBUFFER using the UpdateFrameBufferWithClockImage? function.

*/


/*						Configuring the Code

Configure the following areas to fit your design:

LAT access registers need to be mapped to LED connected pins in the DisplayLEDSTATE function
TIMER0_OFFSET needs to be calibrated for how quickly the firmware should update the computed position. This is usually relative to the rotation speed of the utilized motor.
The SENSOR input is assumed to be on PIN D2. Replace/modify as necessary
The SENSOR triggered logic value is set into LOGIC_VALUE_SENSOR_FIRED
BLANK_LEDSTATES_BETWEEN_SYMBOLS configures the amount of drawn space between digits
SECONDS_START_VALUE can be current time
MINUTES_START_VALUE can be current time
HOURS_START_VALUE can be current time (24 hour clock)
TEXT_VERTICAL_ADJUST can move the text upward to use LEDS further up the line
START_POSITION_OFFSET can adjust how many positions after Position0 we wait before drawing
*/


#include <p18f46K20.h>
#include <timers.h>
#include <delays.h>



// Function Prototypes
void test_flash_leds(void);
void timer0Handler(void);
void timer1Handler(void);
void DisplayLEDSTATE(int STATE);
void UpdateFrameBufferWithClockImage(void);
void InsertIntoFrameBufferFromFontMap(int framebufferIndex, int fontMapIndex);
void Run(void);


// TEXT FORMAT: H_H_:_M_M_:_S_S
//	8 * 5  = 40 LEDSTATES for digits
//	7 * BLANK_LEDSTATES_BETWEEN_SYMBOLS = LEDSTATEs for spacing
#define BLANK_LEDSTATES_BETWEEN_SYMBOLS 3
#define TOTAL_NUMBER_OF_LEDSTATES (40 + 7 * BLANK_LEDSTATES_BETWEEN_SYMBOLS)
int LEDSTATE = 0;
int POSITIONID = 0;
int MILLISECONDS = 0;
int SECONDS = 22;
int MINUTES = 33;
int HOURS = 12;
int FRAMEBUFFER[TOTAL_NUMBER_OF_LEDSTATES];
// Configure this value to tune the interrupt timings of timer0 module
int TIMER0_OFFSET = 63000;
// Configure this value to tune the interrupt timings of timer1 module
// When used with 16MHZ clock, and 1:1 prescaler, this will trigger once a ms
int TIMER1_OFFSET = 61536;
// Configuration for the position sensor's triggered logic value
#define LOGIC_VALUE_SENSOR_FIRED 0
// Configuration for positioning the fontmaps copied to the framebuffer
#define TEXT_VERTICAL_ADJUST 0
// Configuration for positional offset of where to draw framebuffer contents
#define START_POSITION_OFFSET 60


unsigned char FONTMAP[11][5] = {
	{ 
	  0b00000000, 
	  0b11111111, 
	  0b10000001, 
	  0b11111111, 
	  0b00000000 
	},	//0
	
	{ 
	  0b10000110, 
	  0b10000011, 
	  0b11111111, 
	  0b10000000, 
	  0b10000000 
	},	//1
	  
	{ 
	  0b11000001, 
	  0b11100001, 
	  0b10110001, 
	  0b10001101, 
	  0b10001111 
	},	//2
	  	 
	{ 
	  0b10010001, 
	  0b10010001, 
	  0b10010001, 
	  0b11111111, 
	  0b00000000 
	},	//3
	  	 
	{ 
	  0b00011111, 
	  0b00010000, 
	  0b00010000, 
	  0b00010000, 
	  0b11111111 
	},	//4
	  	 
	{ 
	  0b10011111, 
	  0b10010001, 
	  0b10010001, 
	  0b10010001, 
	  0b11110001 
	},	//5
	   	 
	{ 
	  0b11111111, 
	  0b10010001, 
	  0b10010001, 
	  0b10010001, 
	  0b11110000 
	},	//6
	  	 
	{ 
	  0b00000001, 
	  0b00000001, 
	  0b00000001, 
	  0b11111111, 
	  0b00000000 
	},	//7
	  	 
	{ 
	  0b11111111, 
	  0b10010001, 
	  0b10010001, 
	  0b10010001, 
	  0b11111111 
	},	//8
	  	 
	{ 
	  0b00011111, 
	  0b00010001, 
	  0b00010001, 
	  0b00010001, 
	  0b11111111 
	},	//9
	  	 
	{ 
	  0b00000000, 
	  0b00000000, 
	  0b11100111, 
	  0b11100111, 
	  0b00000000 
	}	// :
};

// Configure the interrupt vectors to run our code
#pragma code high_vector_section=0x8
void high_vector (void)
{
	_asm GOTO timer0Handler _endasm
}
#pragma code
#pragma code low_vector_section=0x18
void low_vector (void)
{
	//LATB = 0;
	_asm GOTO timer1Handler _endasm
}
#pragma code



void main (void){ Run(); }
void Run(void){	
	// Configure all I/O Pin tristate registers to OUTPUT
	//		-- Except pin D2 (which is used for position sensor input
	TRISA = 0b00000000;
	TRISB = 0b00000000;
	TRISC = 0b00000000;
	TRISD = 0b00000100;
	TRISE = 0b00000000;
	
	// PORTB has odd pull-ups config. Here's a quick hack to set
	// the starting condition to the same as all other pins
	LATB = 0xFF;
	
	
	// Configure the PIC to use an internal oscillator generated at 16MHZ
	// Set the Oscillator Control Register (datasheet pg 29)
	OSCCONbits.IRCF2 = 1;	// Set the internal oscillator to 16MHZ
	OSCCONbits.IRCF1 = 1;		//
	OSCCONbits.IRCF0 = 1;		//
	OSCCONbits.SCS1  = 1;	// Use non-scaled internal oscillator block
	OSCCONbits.SCS0  = 1;		//
	OSCTUNEbits.PLLEN = 1; // Use PLL multipler to reach 16MHZ

	RCONbits.SBOREN = 0; 	// Disable software brown-out-reset
	RCONbits.IPEN = 1;      //Allow priority levels on interrupts 
	WDTCONbits.SWDTEN = 0;	//Disable the Watchdog timer
		
		
		
	// Configure Timer0 Module
	OpenTimer0(  TIMER_INT_ON &                // Interrupt enabled 
                 T0_16BIT &                  //set timer0 as one 16bit register
                 T0_SOURCE_INT &               //choose internal clock source (TOSC) 
                 T0_PS_1_1                    // Prescale Value: 1:1 
 	);
 	INTCON2bits.TMR0IP = 1;	//Timer0 interrupt priority high
 	INTCONbits.TMR0IE = 1; //Timer0 interrupt enable 
	WriteTimer0(TIMER0_OFFSET);
	
 	// Configure Timer1 Module
	T1CONbits.TMR1ON = 1;		// Enable timer1
    T1CONbits.TMR1CS = 0;		// Use internal clock (FOSC/4)
    T1CONbits.T1SYNC = 1;		// Don't syncronize external clock input
    T1CONbits.T1OSCEN = 0;		// Don't use the Timer1 Oscillator		
    T1CONbits.T1CKPS0 = 1;		//	Timer1 uses a 1:1 prescaler
    T1CONbits.T1CKPS1 = 1;				//	
    T1CONbits.T1RUN = 0;		// main clock DOES NOT run from Timer1
    T1CONbits.RD16 = 1;			//enable 16-bit operation
	
	IPR1bits.TMR1IP = 0; //Timer1 interrupt priority low
 	PIE1bits.TMR1IE = 1; //Timer1 interrupt enable 
	WriteTimer1(TIMER1_OFFSET);

	// Enable monitoring of all interrupts
	INTCONbits.GIE = 1;
			
	//Sit in a while loop, 
	//	- reseting the position variable each time sensor is triggered
	while(1){ 
		if(PORTDbits.RD2 == LOGIC_VALUE_SENSOR_FIRED){ POSITIONID = 0; }
		// Use this one if position sensor not used
		//if(POSITIONID >= TOTAL_NUMBER_OF_LEDSTATES){ POSITIONID =0;	}	
	}	
}

// Timer0 overflow interrupt signals that it is time to:
//	- increment the POSITIONID variable
//	- draw the correct LEDSTATE
// Found this in the C18 errata documentation. This ensures the compiler will use the
// normal RETFIE instruction after interrupt. The other mode (FAST high-priority) is
// known to cause problems when using both high and low inrerrupts.
#pragma interruptlow timer0Handler
void timer0Handler(void){
	// Disable monitoring of all interrupts
	INTCONbits.GIE = 0;
	
	// Increment the POSITIONID
	POSITIONID++;
	
	// Check if we are in the position to draw the contents of FRAMEBUFFER
	if((POSITIONID >= START_POSITION_OFFSET) && 
		((POSITIONID - START_POSITION_OFFSET) <= TOTAL_NUMBER_OF_LEDSTATES)){
		// retrieve the correct LEDSTATE
		LEDSTATE = FRAMEBUFFER[POSITIONID - START_POSITION_OFFSET];
	}
	else{ 
		// Not in position. Blank the LEDS
		LEDSTATE = 0b0000000000000000; 
	}
	
	// Display the new LEDSTATE
	DisplayLEDSTATE(LEDSTATE);
	
	// Clear the timer0 interrupt flag
	INTCONbits.TMR0IF = 0;
	
	// Reset the timer0 module
	WriteTimer0(TIMER0_OFFSET);
	
	// Re-enable the monitoring of interrupts
	INTCONbits.GIE = 1;
	
}	
	
	
// Timer1 overflow interrupt signals that it is time to increment our seconds count
// Found this in the C18 errata documentation. This ensures the compiler will use the
// normal RETFIE instruction after interrupt. The other mode (FAST high-priority) is
// known to cause problems when using both high and low inrerrupts.
#pragma interruptlow timer1Handler
void timer1Handler(void){	
	// Disable monitoring of all interrupts
	INTCONbits.GIE = 0;
	// Clear the timer1 module interrupt flag
	PIR1bits.TMR1IF = 0;
	// Reset the timer1 module immediately
	WriteTimer1(TIMER1_OFFSET);
	
	// Increment the milliseconds count
	MILLISECONDS++;
	
	// Increment the seconds count
	//SECONDS++;
	
	// Handle the flow of time
	if(MILLISECONDS >= 100){ SECONDS++; MILLISECONDS=0; }
	if(SECONDS >= 60){ MINUTES++; SECONDS=0; }
	if(MINUTES >= 60){ HOURS++; MINUTES=0;}
	if(HOURS >= 24){ HOURS=1;}
	
	// Update the framebuffer with the new clock-image
		// Uses the seconds,minutes,hours global variables
	UpdateFrameBufferWithClockImage();

	// Re-enable the monitoring of all interrupts
		// Gotta say, this code depends on UpdateFrameBuffer not 
		// taking too long to run --sturnfie
	INTCONbits.GIE = 1;

}	
	
	
void DisplayLEDSTATE(int STATE){
	// STATE holds the data to map to the LEDs
	// STATE is stored 1=ON, 0=OFF
	// PIC LAT registers must be set opposite of this due to the way
	// 		my LEDS were wired to the micro-controller. I wired my 
	//		LEDS such that configuring the micro-controller to sink 
	//		current (latch the output pin LOW) will illuminate the LED
	// The LED closest to the motor's spinning axis is zero indexed.
	
	// This is inefficient, but hopefully functionally clear.
	//	(more efficient would be using each bit directly as we see them,
	//		instead of creating a temporary structure holding shifted
	//		results of which we use lowest bit and nothing else, for every
	//		offset)
	
	/*
	LATCbits.LATC4 =~ STATE;	// Pulls lowest bit
	LATCbits.LATC5 =~ (STATE >> 1);
	LATCbits.LATC6 =~ (STATE >> 2);
	LATCbits.LATC7 =~ (STATE >> 3);
	LATDbits.LATD4 =~ (STATE >> 4);
	LATDbits.LATD5 =~ (STATE >> 5);
	LATDbits.LATD6 =~ (STATE >> 6);
	LATDbits.LATD7 =~ (STATE >> 7);
	LATBbits.LATB0 =~ (STATE >> 8);	
	LATBbits.LATB1 =~ (STATE >> 9);
	LATBbits.LATB2 =~ (STATE >> 10);
	LATBbits.LATB3 =~ (STATE >> 11);
	LATBbits.LATB4 =~ (STATE >> 12);
	LATBbits.LATB5 =~ (STATE >> 13);
	LATBbits.LATB6 =~ (STATE >> 14);
	LATBbits.LATB7 =~ (STATE >> 15);
	*/
	
	
	
	
	LATBbits.LATB5 =~ STATE;
	LATBbits.LATB4 =~ (STATE >> 1);
	LATBbits.LATB3 =~ (STATE >> 2);	
	LATBbits.LATB2 =~ (STATE >> 3);
	LATBbits.LATB1 =~ (STATE >> 4);
	LATBbits.LATB0 =~ (STATE >> 5);
	LATDbits.LATD7 =~ (STATE >> 6);
	LATDbits.LATD6 =~ (STATE >> 7);
	
}




void UpdateFrameBufferWithClockImage(){
	// Create a temporary index variable
	int frameBufferIndex = 0;
	// Create temporary helper interger variables
	int digitTens, digitOnes = 0;
	
	// Calculate the Tens digit from HOURS
	digitTens = (int)HOURS/10;
	// Calculate the Ones digit from HOURS
	digitOnes = HOURS - 10*digitTens;
	InsertIntoFrameBufferFromFontMap(frameBufferIndex, digitTens);
	// Increment frameBufferIndex by size of fontmap + space
	frameBufferIndex += 5 + BLANK_LEDSTATES_BETWEEN_SYMBOLS;
	InsertIntoFrameBufferFromFontMap(frameBufferIndex, digitOnes);
	// Increment frameBufferIndex by the size of the fontmap + space
	frameBufferIndex += 5 + BLANK_LEDSTATES_BETWEEN_SYMBOLS;
	InsertIntoFrameBufferFromFontMap(frameBufferIndex, 10);		// Insert a Colon
	// Increment frameBufferIndex by size of fontmap + space
	frameBufferIndex += 5 + BLANK_LEDSTATES_BETWEEN_SYMBOLS;
	
	// Calculate the Tens digit from MINUTES
	digitTens = (int)MINUTES/10;
	// Calculate the Ones digit from MINUTES
	digitOnes = MINUTES - 10*digitTens;
	InsertIntoFrameBufferFromFontMap(frameBufferIndex, digitTens);
	// Increment frameBufferIndex by size of fontmap + space
	frameBufferIndex += 5 + BLANK_LEDSTATES_BETWEEN_SYMBOLS;
	InsertIntoFrameBufferFromFontMap(frameBufferIndex, digitOnes);
	// Increment frameBufferIndex by the size of the fontmap + space
	frameBufferIndex += 5 + BLANK_LEDSTATES_BETWEEN_SYMBOLS;
	InsertIntoFrameBufferFromFontMap(frameBufferIndex, 10);		// Insert a Colon
	// Increment frameBufferIndex by size of fontmap + space
	frameBufferIndex += 5 + BLANK_LEDSTATES_BETWEEN_SYMBOLS;
	
	// Calculate the Tens digit from SECONDS
	digitTens = (int)SECONDS/10;
	// Calculate the Ones digit from SECONDS
	digitOnes = SECONDS - 10*digitTens;
	InsertIntoFrameBufferFromFontMap(frameBufferIndex, digitTens);
	// Increment frameBufferIndex by size of fontmap + space
	frameBufferIndex += 5 + BLANK_LEDSTATES_BETWEEN_SYMBOLS;
	InsertIntoFrameBufferFromFontMap(frameBufferIndex, digitOnes);
	// Increment frameBufferIndex by the size of the fontmap + space
	frameBufferIndex += 5 + BLANK_LEDSTATES_BETWEEN_SYMBOLS;

	// the frameBufferIndex should now be equal to the size of the frameBuffer
	//assert(frameBufferIndex+1 == TOTAL_NUMBER_OF_LEDSTATES);
		
}
	
void InsertIntoFrameBufferFromFontMap(int frameBufferIndex, int fontMapIndex){
	// Create temporary iteration variable
	int i = 0;
	
	FRAMEBUFFER[frameBufferIndex] = FONTMAP[fontMapIndex][0];
	FRAMEBUFFER[++frameBufferIndex] = FONTMAP[fontMapIndex][1];
	FRAMEBUFFER[++frameBufferIndex] = FONTMAP[fontMapIndex][2];
	FRAMEBUFFER[++frameBufferIndex] = FONTMAP[fontMapIndex][3];
	FRAMEBUFFER[++frameBufferIndex] = FONTMAP[fontMapIndex][4];
	
	// Add the spaces between symbols
	for(i=0; i<BLANK_LEDSTATES_BETWEEN_SYMBOLS; i++){
		FRAMEBUFFER[++frameBufferIndex] = 0b0000000000000000;
	}	
}


void test_flash_leds(void){
	while(1){
		// LEDS on
		DisplayLEDSTATE(0b0000000000000000);
		// wait
		Delay10KTCYx(20);
		// LEDS off
		DisplayLEDSTATE(0b1111111111111111);
		// wait
		Delay10KTCYx(20);
		
	
	}
}
